#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

#include <aura.h>
#include <auraplus.h>
#include <aura_wchar.h>

#undef NULL
#include "private.h"

struct ShareMemInfo {
    Aura_UInt32       m_uMagicnumberStart;
    int          m_ifd;
    Aura_MemorySize   m_iSize;
    char        *m_szName;
    Aura_Bool      m_bIsOpen;
    Aura_UInt32       m_uMagicnumberEnd;

    ShareMemInfo() :
        m_uMagicnumberStart(MAGIC_START_NUMBER),
        m_ifd(0),
        m_iSize(0),
        m_szName(NULL),
        m_bIsOpen(AURA_FALSE),
        m_uMagicnumberEnd(MAGIC_END_NUMBER)
        {}
    ~ShareMemInfo()
    {
        if(0 != m_ifd){
            close(m_ifd);
        }

        if(!m_bIsOpen){
            //BUGBUG:The creater must call destory function after the last open function be called.
            unlink(m_szName);
        }

        if(NULL != m_szName){
            free(m_szName);
        }
    }
};

Aura_ECode NewShareMemoryFile(char *szName, Aura_MemorySize size, int *pifd)
{
    int fd_create;
    int iZero = 0;

    fd_create = open(szName,
        O_CREAT|O_EXCL|O_RDWR|O_TRUNC,
        S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
    if(-1 == fd_create){
        PERROR("Error:create share memory failed! errno%d. \
        file:%s. line:%d. \n",
        errno, __FILE__, __LINE__);
        if(EEXIST == errno){
            return AURA_E_ALREADY_EXIST;
        }
        return AURA_E_OUT_OF_MEMORY;
    }

    //Extern file size
    if (pwrite(fd_create, &iZero, 1, size ) == -1)
    {
        PERROR("Error: create share memory failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        close(fd_create);
        return AURA_E_OUT_OF_MEMORY;
    }

    *pifd = fd_create;

    return AURA_NOERROR;
}

Aura_ECode OpenShareMemoryFile(char *szName, int *pifd)
{
    int fd_open;

    fd_open = open(szName,
        O_RDWR);
    if(-1 == fd_open){
        perror("ERR:");
        PERROR("Error:Open share memory failed! errno%d. \
        file:%s. line:%d. \n",
        errno, __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    *pifd = fd_open;

    return AURA_NOERROR;
}


AURA_API AuraSharedMemory_Create(
        /* [in] */ Aura_PWChar name,
        /* [in] */ Aura_MemorySize size,
        /* [in] */ Aura_Bool bCommit,
        /* [out] */ Aura_Handle * phSharedMemory,
        /* [out] */ Aura_Bool * pbAlreadyExisted)
{
    Aura_ECode ec = AURA_NOERROR;
    int fd = 0;
    ShareMemInfo *pShareMem;

    assert(!(name == NULL || *name == 0));
    assert(NULL != phSharedMemory);

    pShareMem = new ShareMemInfo();
    if(NULL == pShareMem){
        PERROR("Error: Aura Shared Memory Create failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_OUT_OF_MEMORY;
    }

    pShareMem->m_szName = (char *)malloc(Aura_Wcslen((Aura_PWChar)name)+1);
    if(NULL == pShareMem->m_szName){
        PERROR("Error: Aura Shared Memory Create failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        delete pShareMem;
        return AURA_E_OUT_OF_MEMORY;
    }
    UnicToAnsi(pShareMem->m_szName, (const Aura_PWChar)name);

    ec = NewShareMemoryFile(pShareMem->m_szName, size, &fd);

    if(AURA_FAILED(ec)){
        PERROR("Error: Aura Shared Memory Create failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        delete pShareMem;
        return ec;
    }

    pShareMem->m_ifd = fd;

    pShareMem->m_iSize = size;

    *phSharedMemory = pShareMem;

    return AURA_NOERROR;
}

EXTERN_C int AuraMemoryProtectionConvertToLinux(Aura_MemoryProtection protect);

AURA_API AuraSharedMemory_Attach(
        /* [in] */ Aura_Handle hSharedMemory,
        /* [in] */ Aura_MemoryProtection protect,
        /* [out] */ Aura_Address * pAddress)
{
    ShareMemInfo *pShareMem;
    Aura_PVoid pBase;
    int iProt;

    pShareMem = (ShareMemInfo *)hSharedMemory;

    if((NULL == pShareMem)
        || (!IsValidHandle(ShareMemInfo, pShareMem))){
        PERROR("Error: AuraSharedMemory_Attach failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }


    iProt = AuraMemoryProtectionConvertToLinux(protect);

    pBase = mmap( 0,
                (size_t)pShareMem->m_iSize,
                iProt, MAP_SHARED,
                pShareMem->m_ifd,
                0);
    if(MAP_FAILED == pBase){
        PERROR("Error: AuraSharedMemory_Attach failed! \
            file:%s. line:%d.\n", __FILE__, __LINE__);
        return AURA_E_INVALID_OPERATION;
    }

    *pAddress = (Aura_Address)pBase;

    return AURA_NOERROR;
}

AURA_API AuraSharedMemory_Detach(
        /* [in] */ Aura_Handle hSharedMemory,
        /* [in] */ Aura_Address address)
{
    ShareMemInfo *pShareMem;
    int ret;

    if((NULL == hSharedMemory)
        || (!IsValidHandle(ShareMemInfo, hSharedMemory))){
        PERROR("Error: AuraSharedMemory_Detach failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    pShareMem = (ShareMemInfo *)hSharedMemory;

    ret = munmap((Aura_PVoid)address, (size_t)pShareMem->m_iSize);
    if(-1 == ret){
        PERROR("Error: AuraSharedMemory_Detach failed! \
            file:%s. line:%d.\n", __FILE__, __LINE__);
        return AURA_E_INVALID_OPERATION;
    }

    return AURA_NOERROR;
}

AURA_API AuraSharedMemory_Destroy(
        /* [in] */ Aura_Handle hSharedMemory)
{
    ShareMemInfo *pShareMem;

    if((NULL == hSharedMemory)
        || (!IsValidHandle(ShareMemInfo, hSharedMemory))){
        PERROR("Error: AuraSharedMemory_Destroy failed! \
        file:%s. line:%d. \n",
        __FILE__, __LINE__);
        return AURA_E_INVALID_ARGUMENT;
    }

    pShareMem = (ShareMemInfo *)hSharedMemory;

    delete pShareMem;

    return AURA_NOERROR;
}

